package application.anno.impl;

import application.anno.Injection;
import application.manager.InjectionManager;
import application.util.ReflectionsUtil;
import lombok.Setter;

import java.lang.reflect.*;
import java.util.*;

/**
 * 提供了一个优先注入的通道，开发者可以根据程序的实际开发情况，自定义Injection注解的注入顺序。
 */
public class InjectionImpl {

    @Setter
    private FirstInjection firstInjection = new FirstInjection() {
        @Override
        public void firstInjection() {

        }
    };

    /**
     * 通过cls 生成对应的对象。
     *
     * @param cls 索要对象的类对象
     * @param <T> 对象的类型， 它与cls的泛型一致
     * @return cls生成的对象
     * @throws IllegalAccessException    非法访问异常
     * @throws InvocationTargetException 反射目标异常
     * @throws InstantiationException    实例化异常
     */
    public <T> T getObjectByConstructor(Class<?> cls) throws IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors();
        for (Constructor<?> constructor : declaredConstructors) {
            if (constructor.getParameterCount() == 0) {
                if (constructor.getModifiers() != Modifier.PUBLIC) {
                    constructor.setAccessible(true);
                }
                return (T) constructor.newInstance();
            }
        }
        return null;
    }

    public <T> T getObjectByMethod(Class<T> cls, String methodName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method declaredMethod = cls.getDeclaredMethod(methodName);
        declaredMethod.setAccessible(true);
        return (T) declaredMethod.invoke(null);
    }

    public void injectClass() throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
        Set<Class<?>> annotationByClass = ReflectionsUtil.getAnnotationByClass(Injection.class);
        for (Class<?> cls : annotationByClass) {
            Injection declaredAnnotation = cls.getDeclaredAnnotation(Injection.class);
            if (!(declaredAnnotation.ip().isEmpty() || declaredAnnotation.port() == -1 || declaredAnnotation.resource().isEmpty())) {
                continue;
            }
            Object o;
            if (declaredAnnotation.staticMethodName().isEmpty()) {
                o = getObjectByConstructor(cls);
            } else {
                o = getObjectByMethod(cls, declaredAnnotation.staticMethodName());
            }
            InjectionManager.getInstance().register(o.getClass(), o);
        }
    }

    public void injectionField(Set<Field> fields, Object o) throws IllegalAccessException {
        if (fields == null || fields.isEmpty() || o == null) {
            return;
        }
        Set<Field> fieldsCopy = new HashSet<>();
        fieldsCopy.addAll(fields);
        List<Field> fieldsList = Arrays.asList(o.getClass().getDeclaredFields());
        fieldsCopy.retainAll(fieldsList);
        InjectionManager instance = InjectionManager.getInstance();
        for (Field field : fieldsCopy) {
            field.setAccessible(true);
            Object object = field.get(o);
            if (object != null) {
                continue;
            }
            Object lookup = instance.lookup(field.getType());
            if (lookup == null) {
                lookup = instance.lookup(field.getType().getSuperclass());
            }
            field.set(o, lookup);
        }
    }

    public void injectField() throws IllegalAccessException {
        Set<Field> annotationByField = ReflectionsUtil.getAnnotationByField(Injection.class);
        Map<Class<?>, Object> injections = InjectionManager.getInstance().getInjections();
        for (Object o : injections.values()) {
            injectionField(annotationByField, o);
        }
    }

    public void injectionField() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        firstInjection.firstInjection();
        injectClass();
        injectField();
    }

    public void injectionField(Object object) throws IllegalAccessException {
        List<Field> fields = Arrays.asList(object.getClass().getDeclaredFields());
        Set<Field> annotationByField = ReflectionsUtil.getAnnotationByField(Injection.class);
        annotationByField.retainAll(fields);
        for (Field field : annotationByField) {
            Class<?> type = field.getType();
            Object lookup = InjectionManager.getInstance().lookup(type);
            field.setAccessible(true);
            field.set(object, lookup);
        }
    }

    public interface FirstInjection {

        void firstInjection();
    }
}
